package com.jeesite.modules.file.web;

import com.jeesite.common.config.Global;
import com.jeesite.common.io.FileUtils;
import com.jeesite.common.lang.StringUtils;
import com.jeesite.common.web.BaseController;
import com.jeesite.modules.file.entity.FileEntity;
import com.jeesite.modules.file.entity.FileUpload;
import com.jeesite.modules.file.service.FileUploadService;
import com.jeesite.modules.oss.client.properties.MinioProperties;
import io.minio.MinioClient;
import io.minio.errors.*;
import org.jodconverter.core.DocumentConverter;
import org.jodconverter.core.office.OfficeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * 文件预览controller
 * 参考：jeesite-module-filepreview-4.3.3-20220214.132756-1.jar com.jeesite.modules.file.web.FilePreviewController
 * jeesite-module-filepreview-4.3.3-20220214.132756-1.jar
 * @author zhengYaJun
 * @version 2022-2-16
 */
@Controller
public class FilePreviewController extends BaseController {

    @Autowired(required = false)
    private DocumentConverter converter;

    @Resource
    private FileUploadService fileUploadService;

    @Autowired
    private MinioProperties properties;
    @Autowired
    private MinioClient minioClient;

    public FilePreviewController() {
    }

    @RequestMapping({"/file/true/preview"})
    public String preview(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String fileUrl = (String) request.getAttribute("fileUrl");
        String fileUri = (String) request.getAttribute("fileUri");
        String filePath = (String) request.getAttribute("filePath");
        String fileUrls = (String) request.getAttribute("fileUrls");

        // 获取请求前缀
        String contextPath = request.getRequestURL().toString().replace("/file/true/preview", "");

        String docType = "";
        String docName = StringUtils.lowerCase(filePath);
        if (StringUtils.endsWithAny(docName, new CharSequence[]{".gif", ".bmp", ".jpeg", ".jpg", ".png", ".ico"})) {
            docType = "img";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".doc", ".docx"})) {
            docType = "doc";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".xls", ".xlsx"})) {
            docType = "xls";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".ppt", ".pptx"})) {
            docType = "ppt";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".wps", ".wpt"})) {
            docType = "wps";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".tif", "tiff"})) {
            docType = "tif";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".txt"})) {
            docType = "txt";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".pdf"})) {
            docType = "pdf";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".mp3", ".ogg", ".wav"})) {
            docType = "mp3";
        } else if (StringUtils.endsWithAny(docName, new CharSequence[]{".mp4", ".webm", ".mkv"})) {
            docType = "mp4";
        }

        // 这里pdf文件也需要从远程下载到本地
        if (this.converter != null && StringUtils.inString(docType, new String[]{"doc", "xls", "ppt", "pdf","txt","img"})) {
            FileUpload fileUpload = fileUploadService.get(new FileUpload(request.getParameter("uid")));
            FileEntity fileEntity = fileUpload.getFileEntity();
            String fileName = fileEntity.getFileId() + "." + fileEntity.getFileExtension();
            String localFile = "";
            try {
                localFile = getRemoteFile(fileEntity,docType);
                fileUrl = contextPath + "/userfiles/fileupload/" + fileEntity.getFilePath() + "/" + fileName;
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("下载远程文件错误！", e.getMessage());
            }

            File file = new File(localFile);
            // 这里排除pdf，为了避免文件再次转换
            if (file.exists() && !"pdf".equals(docType)) {
                docType = StringUtils.inString(docType, new String[]{"xls"}) ? "html" : "pdf";
                File outFile = new File(file.getAbsoluteFile() + "." + docType);
                if (!outFile.exists()) {
                    try {
                        this.converter.convert(file).to(outFile).execute();
                    } catch (OfficeException var12) {
                        this.logger.error(file.toString(), var12);
                    }
                }

                if (fileUrl != null) {
                    int i = fileUrl.indexOf("?");
                    if (i != -1) {
                        fileUrl = fileUrl.substring(0, i) + "." + docType + fileUrl.substring(i);
                    } else {
                        fileUrl = fileUrl + "." + docType;
                    }
                    fileUrl += "?source=preview";
                }
            }
            // 这里给原本就是pdf文件的也加个后缀，支持edge浏览器预览
            if (filePath.endsWith(".pdf")) {
                fileUrl += "?source=preview";
            }
        }

        request.setAttribute("docType", docType);
        request.setAttribute("fileUrl", fileUrl);
        request.setAttribute("fileUri", fileUri);
        request.setAttribute("filePath", filePath);
        request.setAttribute("fileUrls", fileUrls);
        return "modules/file/filePreview";
    }

    /**
     * 获取远程文件
     * @param fileEntity
     * @return
     * @throws IOException
     * @throws InvalidBucketNameException
     * @throws InsufficientDataException
     * @throws ErrorResponseException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws InvalidResponseException
     * @throws XmlParserException
     * @throws InternalException
     */
    private String getRemoteFile(FileEntity fileEntity,String docType) throws IOException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, ServerException {
        String fileName = "fileupload/" + fileEntity.getFilePath() + "/" + fileEntity.getFileId() + "." + fileEntity.getFileExtension();
        String localFile = Global.getUserfilesBaseDir(fileName);
        File file = new File(localFile);
        if (file.exists()) {
            file.delete();
        }

        String bucketName = properties.getBucketName();
        String url = "";
        if (StringUtils.isNotBlank(properties.getBaseDir())) {
            url = url + properties.getBaseDir() + "/";
        }
        url = url + "fileupload/" + fileEntity.getFilePath();
        String objectName = FileUtils.path(url) + fileEntity.getFileId() + "." + fileEntity.getFileExtension();
        InputStream inputStream = minioClient.getObject(bucketName, objectName);
        FileUtils.copyInputStreamToFile(inputStream, file);

        //txt文件格式转码
        if(docType.equals("txt")){
            saveFile2Charset(localFile,"UTF-8");
        }
        return localFile;
    }

    /**
     *   txt文件转码
     * @Param: [filePath, toCharsetName]
     * @return: void
     */
    public void saveFile2Charset(String filePath,String toCharsetName) throws IOException {
        if(StringUtils.isEmpty(toCharsetName)){
            toCharsetName = "UTF-8";
        }
        StringBuffer buffer = new StringBuffer();
        BufferedReader br = null;
        OutputStreamWriter outWrite = null;
        BufferedWriter bw = null;
        try {
            File file = new File(filePath);
            if(file.exists()){
                //获取文件编码格式
                String charset = getFilecharset(filePath);

                //以文件编码格式读取文件流
                InputStreamReader isr = new InputStreamReader(new FileInputStream(file),charset);
                br = new BufferedReader(isr);
                String lineTxt;
                while ((lineTxt = br.readLine())!=null ){
                    buffer.append(lineTxt).append("\n");
                }
                br.close();
                // file.delete();

                //转为指定字符集
                String charsetStr = new String(buffer.toString().getBytes(toCharsetName),toCharsetName);
                //写入文件
                OutputStream outputStream = new FileOutputStream(filePath);
                outWrite = new OutputStreamWriter(outputStream,toCharsetName);
                bw = new BufferedWriter(outWrite);

                bw.write(charsetStr);

                bw.close();
                outWrite.close();
            }
        }finally {
            if(br!=null){
                br.close();
            }
            if (outWrite!=null){
                outWrite.close();
            }
            if(bw!=null){
                bw.close();
            }
        }
    }

    //判断编码格式方法
    private String getFilecharset(String filePath) {
        String charset = "GBK";
        byte[] first3Bytes = new byte[3];
        try {
            boolean checked = false;
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
            bis.mark(0);
            int read = bis.read(first3Bytes, 0, 3);
            if (read == -1) {
                return charset; //文件编码为 ANSI
            } else if (first3Bytes[0] == (byte) 0xFF
                    && first3Bytes[1] == (byte) 0xFE) {
                charset = "UTF-16LE"; //文件编码为 Unicode
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xFE
                    && first3Bytes[1] == (byte) 0xFF) {
                charset = "UTF-16BE"; //文件编码为 Unicode big endian
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xEF
                    && first3Bytes[1] == (byte) 0xBB
                    && first3Bytes[2] == (byte) 0xBF) {
                charset = "UTF-8"; //文件编码为 UTF-8
                checked = true;
            }
            bis.reset();
            if (!checked) {
                int loc = 0;
                while ((read = bis.read()) != -1) {
                    loc++;
                    if (read >= 0xF0)
                        break;
                    if (0x80 <= read && read <= 0xBF) // 单独出现BF如下的，也算是GBK
                        break;
                    if (0xC0 <= read && read <= 0xDF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF)
                            // (0x80
                            // - 0xBF),也可能在GB编码内
                            continue;
                        else
                            break;
                    } else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错，可是概率较小
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            read = bis.read();
                            if (0x80 <= read && read <= 0xBF) {
                                charset = "UTF-8";
                                break;
                            } else
                                break;
                        } else
                            break;
                    }
                }
            }
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return charset;
    }
}
